/*
 * Copyright 2011 The Rusted Roof, LLC.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *        http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */

package org.proteusframework.api.base;

import org.proteusframework.api.IPluginRegistrar;
import org.proteusframework.api.model.*;
import org.proteusframework.spi.IRegistrar;
import org.proteusframework.util.Assert;

import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Base class for all {@link IPluginRegistrar} objects.
 *
 * @author Tacoma Four
 */
public abstract class AbstractPluginRegistrar extends AbstractNamedObject implements IPluginRegistrar
{
    private static final Logger logger = Logger.getLogger(AbstractPluginRegistrar.class.getCanonicalName());

    /**
     * Maps the <code>IQualifiedName</code> to its associated <code>IRegistrar</code>.
     */
    protected final Map<IQualifiedName, IRegistrar> registrar = new HashMap<IQualifiedName, IRegistrar>();

    /**
     * Maps the <code>IQualifiedName</code> to its associated version collection. In other words, one
     * plugin may optionally support more than one plugin API version concurrently.
     */
    protected final Map<IQualifiedName, IVersionCollection> versions = new HashMap<IQualifiedName, IVersionCollection>();

    /**
     * Immutable version collection defined by the plugin archetype's PluginRegistrar.
     */
    protected final IVersionCollection versionCollection;

    /**
     * Constructs an abstract plugin registrar object associated with the immutable <code>IDisplayableName</code>
     * that supports the declared version collection.
     *
     * @param displayableName   Displayable name
     * @param versionCollection Collection of versions that this plugin registrar is capable of producing
     */
    protected AbstractPluginRegistrar(IDisplayableName displayableName, IVersionCollection versionCollection)
    {
        super(displayableName);

        Assert.parameterNotNull(versionCollection, "Parameter 'versionCollection' must not be null");

        this.versions.put(displayableName, versionCollection);

        this.versionCollection = versionCollection;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public IVersionCollection getVersionCollection(IQualifiedName qualifiedName) throws NoSuchQualifiedNameException
    {
        if (!isInstalled(qualifiedName))
            throw new NoSuchQualifiedNameException(qualifiedName);

        return versions.get(qualifiedName);
    }


    /**
     * {@inheritDoc}
     */
    @Override
    public boolean isInstalled(IVersion version)
    {
        boolean found = false;

        Assert.parameterNotNull(version, "Parameter 'version' must not be null");

        for (IRegistrar factory : registrar.values())
        {
            if (factory.supportsVersion(version))
            {
                found = true;
                break;

            }
        }

        return found;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean isInstalled(IQualifiedName qualifiedName)
    {
        Assert.parameterNotNull(qualifiedName, "Parameter 'qualifiedName' must not be null");
        IRegistrar factory = registrar.get(qualifiedName);
        return (null != factory);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean isInstalled(IVersionDescriptor versionDescriptor)
    {
        boolean isInstalled = false;

        Assert.parameterNotNull(versionDescriptor, "Parameter 'versionDescriptor' must not be null");

        if (isInstalled(versionDescriptor.getQualifiedName()))
        {
            IRegistrar factory = registrar.get(versionDescriptor.getQualifiedName());
            if (null != factory)
                isInstalled = factory.supportsVersion(versionDescriptor.getVersion());
        }

        return isInstalled;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Collection<IQualifiedName> qualifiedNames()
    {
        Collection<IQualifiedName> list = new ArrayList<IQualifiedName>();

        for (IRegistrar registered : registrar.values())
            list.add(registered.getDisplayableName());

        return list;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Set<IVersion> versions()
    {
        Set<IVersion> versionListing = new TreeSet<IVersion>();

        for (IVersionCollection versionCollection : versions.values())
            versionListing.addAll(versionCollection.supportedVersions());

        return versionListing;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Collection<IVersion> list(IQualifiedName qualifiedName) throws NoSuchQualifiedNameException
    {
        Collection<IVersion> versionList = new ArrayList<IVersion>();

        Assert.parameterNotNull(qualifiedName, "Parameter 'qualifiedName' must not be null");

        if (isInstalled(qualifiedName))
        {
            IRegistrar pluginRegistrar = registrar.get(qualifiedName);
            versionList.addAll(pluginRegistrar.supportedVersions());
        } else
        {
            throw new NoSuchQualifiedNameException(qualifiedName);
        }

        return versionList;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public <T extends IPlugin> T createPlugin(final IVersion version, final Class<T> expectedInterface) throws NoSuchVersionException
    {
        Assert.parameterNotNull(version, "Parameter 'version' must not be null");

        T plugin = null;

        if (isInstalled(version))
        {
            if (logger.isLoggable(Level.FINE))
                logger.fine("Creating default plugin for version " + version.toString());

            for (final IRegistrar factory : registrar.values())
            {
                if (factory.supportsVersion(version))
                {
                    IVersionDescriptor versionDescriptor = new VersionDescriptor(factory.getDisplayableName(), version);

                    plugin = factory.create(versionDescriptor, expectedInterface);
                    plugin.setPlatformDelegate(delegate);

                    break;
                }
            }

        } else
        {
            throw new NoSuchVersionException(version, "The requested version was not installed");
        }

        if (null == plugin)
            throw new NoSuchVersionException(version, "Failed to identify the given version in the registrar");

        return plugin;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public IVersionCollection getVersionCollection()
    {
        return this.versionCollection;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public <T extends IPlugin> T createPlugin(IVersionDescriptor versionDescriptor, Class<T> expectedInterface)
            throws NoSuchQualifiedNameException, NoSuchVersionException
    {
        Assert.parameterNotNull(versionDescriptor, "Parameter 'versionDescriptor' must not be null");
        Assert.parameterNotNull(versionDescriptor.getVersion(), "Parameter 'versionDescriptor.getVersion()' must not be null");

        if (null == versionDescriptor.getQualifiedName())
        {
            return createPluginByVersion(versionDescriptor, expectedInterface);
        } else
        {
            return createPluginByVersionAndQualifiedName(versionDescriptor, expectedInterface);
        }
    }

    protected <T extends IPlugin> T createPluginByVersion(IVersionDescriptor versionDescriptor, Class<T> expectedInterface)
            throws NoSuchQualifiedNameException, NoSuchVersionException
    {
        T plugin = null;

        IVersion version = versionDescriptor.getVersion();

        //
        // Only verifies the presence of the version, not the configuration restrictions
        //
        if (isInstalled(version))
        {

            for (final IRegistrar factory : registrar.values())
            {
                if (factory.supportsVersion(version))
                {
                    plugin = factory.create(versionDescriptor, expectedInterface);
                    plugin.setPlatformDelegate(delegate);

                    break;
                }
            }

            Assert.isNotNull(plugin);

        } else
        {
            if (isInstalled(versionDescriptor.getQualifiedName()))
            {
                logger.warning("Plugin Registrar has no record of a plugin version " +
                        versionDescriptor.getVersion().toString());

                throw new NoSuchVersionException(versionDescriptor);
            } else
            {
                logger.warning("Plugin Registrar has no record of a plugin " +
                        versionDescriptor.getQualifiedName().toString());

                throw new NoSuchQualifiedNameException(versionDescriptor.getQualifiedName());
            }

        }

        return plugin;
    }

    protected <T extends IPlugin> T createPluginByVersionAndQualifiedName(IVersionDescriptor versionDescriptor, Class<T> expectedInterface)
            throws NoSuchQualifiedNameException, NoSuchVersionException
    {
        T plugin;

        //
        // Only verifies the presence of the version, not the configuration restrictions
        //
        if (isInstalled(versionDescriptor))
        {
            if (logger.isLoggable(Level.FINE))
                logger.fine("Creating " + versionDescriptor.toString());

            IRegistrar pluginRegistrar = registrar.get(versionDescriptor.getQualifiedName());
            plugin = pluginRegistrar.create(versionDescriptor, expectedInterface);
            plugin.setPlatformDelegate(delegate);

        } else
        {
            if (isInstalled(versionDescriptor.getQualifiedName()))
            {
                logger.warning("Plugin Registrar has no record of a plugin version " +
                        versionDescriptor.getVersion().toString());

                throw new NoSuchVersionException(versionDescriptor);
            } else
            {
                logger.warning("Plugin Registrar has no record of a plugin " +
                        versionDescriptor.getQualifiedName().toString());

                throw new NoSuchQualifiedNameException(versionDescriptor.getQualifiedName());
            }

        }

        return plugin;
    }
}
